﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Noris.Tools.TraceVisualiser.Support
{
	#region class StringHeap : centrální skladiště stringů
	/// <summary>
	/// Skladiště textů (String).
	/// Je vhodné jej používat, pokud očekáváme časté ukládání shodných řetězců.
	/// Toto skladiště do sebe pojme String a vrátí za něj Int32 (jako lístek do šatny),
	/// a naopak dokáže vydat odpovídající String (jako kabát) při předání Int32 (onoho lístu do šatny).
	/// Na rozdíl od šatny ale při podání Stringu, který už je v skladišti uložen, neukládá tento String podruhé, 
	/// ale vrátí shodný Int32 jako při prvním uložení (každý unikátní String ukládá jen jednou).
	/// Navíc některé jednoduché Stringy (null, empty, jednoznakové stringy) vlastně neukládá vůbec,
	/// ale využívá záporných hodnot Int32 pro popis těchto jednoduchých Stringů.
	/// Na základě těchto záporných hodnot pak kdykoli zpětně sestaví potřebný string.
	/// Uložení Stringu zabírá kupu paměti (string "UPDATE" zabere 26 Byte). Uložení Int32 zabere 4 Byte.
	/// </summary>
    public class StringHeap
    {
		#region Konstrukce, základní property
        public StringHeap()
        {
            this.Axis = new Dictionary<int, StringAxis>();
        }
        protected Dictionary<int, StringAxis> Axis;
		/// <summary>
		/// Počet jednotlivých položek (=unikátní stringy), uložených v tomto objektu
		/// </summary>
        public int ItemCount
        {
            get
            {
                int cnt = 0;
                foreach (StringAxis axis in this.Axis.Values)
                    cnt += axis.ItemCount;
                return cnt;
            }
        }
		/// <summary>
		/// Kompletně vyčistí celou svoji paměť.
		/// </summary>
		public void ClearMemory()
		{
			foreach (StringAxis axis in this.Axis.Values)
				axis.ClearMemory();
			this.Axis.Clear();
		}
		#endregion
		#region Výměna Stringu za Pointer a naopak
		/// <summary>
		/// Vrátí StringPtr pro daný string.
		/// Vrácený objekt třídy StringPtr má velikost 16 Byte, obsahuje i referenci na this (=StringHeap),
		/// a dokáže sám vrátit string bez jakékoli další pomoci.
		/// Nehodí se pro veliké množství stringů, tam se více hodí ukládání hodnot Int32, vrácený z metody this.GetInt32()
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
        internal StringPtr GetPointer(string value)
        {
            return new StringPtr(this, GetInt32(value));
        }
		/// <summary>
		/// Vrátí klíč stringu (int pointer), pod kterým lze kdykoliv následně získat originální string (voláním metody GetString).
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
        public Int32 GetInt32(string value)
        {
            int pointer = 0;
            StringInfo info = new StringInfo(value);
			if (info.IsSimple)
				pointer = info.Pointer;
			else
			{
				int group = info.Group;
				StringAxis axis;
				if (!this.Axis.TryGetValue(group, out axis))
				{
					axis = new StringAxis(group);
					this.Axis.Add(group, axis);
				}
				pointer = axis.GetPointer(info);
			}
            return pointer;
        }
		/// <summary>
		/// Vrátí string, který je uložen pod daným klíčem (pointerem).
		/// </summary>
		/// <param name="pointer"></param>
		/// <returns></returns>
        public string GetString(int pointer)
        {
            if (pointer <= 0)
                return StringInfo.CreateString(pointer);
            int group = StringInfo.CreateGroup(pointer);

            StringAxis axis;
            if (this.Axis.TryGetValue(group, out axis))
                return axis.GetString(pointer);

            return null;
        }
        /// <summary>
        /// Jednoduše a bez čtení konkrétních dat (z interní databáze textů) zjistí, zda string na který odkazuje daný pointer je Null nebo Empty.
        /// </summary>
        /// <param name="pointer"></param>
        /// <returns></returns>
        public static bool IsNullOrEmptyString(int pointer)
        {
            return (pointer <= 0);
        }
        /// <summary>
        /// Jednoduše a bez čtení konkrétních dat (z interní databáze textů) zjistí, zda string na který odkazuje daný pointer je neprázdný (není null a obsahuje něco víc než mezery).
        /// </summary>
        /// <param name="pointer"></param>
        /// <returns></returns>
        public static bool IsNotEmpty(int pointer)
        {
            return (pointer > 0);
        }
        #endregion
		#region Podpora pro Array a Listy
		/// <summary>
		/// Vrátí pole stringů na základě pole pointerů
		/// </summary>
		/// <param name="pointers"></param>
		/// <returns></returns>
		public string[] GetStrings(IEnumerable<Int32> pointers)
		{
			if (pointers == null) return null;
			return GetStringList(pointers).ToArray();
		}
		/// <summary>
		/// Vrátí pole stringů na základě pole pointerů
		/// </summary>
		/// <param name="pointers"></param>
		/// <returns></returns>
		public List<string> GetStringList(IEnumerable<Int32> pointers)
		{
			if (pointers == null) return null;
			List<string> strings = new List<string>();
			foreach (Int32 pointer in pointers)
				strings.Add(GetString(pointer));
			return strings;
		}
		/// <summary>
		/// Vrátí pole pointerů na základě pole stringů
		/// </summary>
		/// <param name="strings"></param>
		/// <returns></returns>
		public Int32[] GetPointers(IEnumerable<string> strings)
		{
			if (strings == null) return null;
			return GetPointerList(strings).ToArray();
		}
		/// <summary>
		/// Vrátí List pointerů na základě pole stringů
		/// </summary>
		/// <param name="strings"></param>
		/// <returns></returns>
		public List<Int32> GetPointerList(IEnumerable<string> strings)
		{
			if (strings == null) return null;
			List<Int32> pointers = new List<int>();
			foreach (String value in strings)
				pointers.Add(GetInt32(value));
			return pointers;
		}
		#endregion
		#region class StringAxis : úložiště stringů, typicky jedné grupy (skupina podle shodného počátečního písmena)
		/// <summary>
		/// StringAxis : úložiště stringů, typicky jedné grupy (skupina podle shodného počátečního písmena)
		/// </summary>
		protected class StringAxis
        {
            internal StringAxis(int group)
            {
                this.Group = group;
                this.Keys = new Dictionary<StringInfo, int>();
                this.Values = new List<StringInfo>();
            }
            public int ItemCount
            {
                get
                {
                    return this.Values.Count;
                }
            }
            protected int Group;
            protected Dictionary<StringInfo, int> Keys;
            protected List<StringInfo> Values;
            /// <summary>
            /// Pro daný vstupující text najde a vrátí Pointer.
            /// Pokud tento text ještě nemá v paměti, přidá si jej a vrátí nově vytvořený Pointer.
            /// </summary>
            /// <param name="find"></param>
            /// <returns></returns>
            internal int GetPointer(StringInfo find)
            {
                int index;
                if (this.Keys.TryGetValue(find, out index))
                    return this.Values[index].Pointer;
                
                index = this.Values.Count;
                find.Index = index;
                this.Keys.Add(find, index);
                this.Values.Add(find);
                return find.Pointer;
            }
            /// <summary>
            /// Pro daný pointer najde a vrátí string
            /// </summary>
            /// <param name="pointer"></param>
            /// <returns></returns>
            internal string GetString(int pointer)
            {
                int index = pointer & 0x7FFFFF;          // 8 000 000
                if (index >= 0 && index < this.Values.Count)
                    return this.Values[index].Value;
                return null;
            }
			/// <summary>
			/// Kompletně vyčistí celou svoji paměť.
			/// </summary>
			public void ClearMemory()
			{
				this.Keys.Clear();
				this.Values.Clear();
			}
		}
		#endregion
		#region class StringInfo : úložiště jednoho konkrétního stringu + statické metody pro konverzi jednoduchých stringů
		/// <summary>
        /// Třída pro uložení informací o jednom stringu, a pokud je string prázdný tak pouze metadata o něm.
		/// Jedna instance této třídy představuje jeden unikátní string, který se ukládá do slovníku.
        /// </summary>
        protected class StringInfo
        {
            #region Konstrukce, uložení stringu, Group, Hash
            internal StringInfo(string value)
            {
                this._Value = null;
				this.Hash = 0;
				this.Pointer = 0;
				if (value == null)
                {	// Null má pointer 0:
                }
				else if (value.Trim(' ').Length == 0)
				{	// Samé mezery: ukládáme záporný pointer, jehož hodnota = -( 65537 + délka stringu ):
					this.Pointer = -(65537 + value.Length);
				}
				else if (value.Length == 1)
				{	// String (ne mezery) s délkou 1 znak: uložíme do Pointeru jeho zápornou (hodnotu zvýšenou o 1) (aby char(0) bylo uloženo jako -1):
					this.Pointer = -(((int)value[0]) + 1);
				}
				else
				{	// Standardní string, bude se ukládat:
					this._Value = value;
					this.Hash = value.GetHashCode();
					// Jako základ Group vezmu první neprázdný znak (vždycky nějaký bude, jinak bych byl v nějaké předchozí větvi):
					this.Group = ((int)value.TrimStart(' ')[0]); // Group.set zajistí omezení hodnoty na 1-255 a její posunutí o 23 bitů doleva a uložení do Pointeru
				}
            }
            internal string Value { get { return this._GetValue(); } }
            private string _GetValue()
            {
				if (IsSimple)
					return CreateString(this.Pointer);
				return _Value;
            }
            internal string _Value;
			/// <summary>
			/// Hash code, má význam pro indexování. Pokud jde o string, který se ukládá do indexu (má nejednoduchý string),
			/// pak se jeho has uloží sem, aby se nemusel (typicky z dlouhých stringů) počítat při každém dotazu na HashCode v metodě GetHashCode().
			/// </summary>
            internal Int32 Hash { get; private set; }
			/// <summary>
			/// Pointer: fyzicky uložené číslo.
			/// Pointer může být 0 = pak reprezentuje null.
			/// Pointer může být kladný = pak reprezentuje ukládaný nejednoduchý string, který se najde někde ve slovníku.
			/// Pointer může být záporný, pak reprezentuje Empty string určité délky, anebo jednoznakový text.
			/// Hodnota 0 a záporné jsou určeny v konstruktoru.
			/// Hodnota kladná je zčásti určena v konstruktoru (složka Group = bity 24,25,26,27,28,29,30), a zčásti přichází v property Index (viz tam).
			/// Základním cílem snah je minimalizovat data fyzicky uložená v this objektu.
			/// </summary>
			internal int Pointer { get; private set; }
			/// <summary>
			/// Vrací true, když this objekt je Simple = stačí nám Pointer, není třeba objekt ukládat do paměti.
			/// Pointer sám stačí k tomu, abychom z něj vytvořili původní string.
			/// Jde o stringy, které jsou null (Pointer = 0), 
			/// které jsou jednoznakové (Pointer = - hodnota znaku - 1),
			/// anebo které jsou složené pouze z určitého počtu mezer (pak pointer = - počet mezer - 65537).
			/// Kladný pointer nese specifický string a je třeba jej ukládat, není Simple.
			/// </summary>
			internal bool IsSimple { get { return (this.Pointer <= 0); } }
            /// <summary>
            /// Skupina stringu: 0 = IsSimple, +1 až +255 odpovídá prvnímu znaku stringu.
			/// Slouží k rozřazení stringů do určitého počtu Axis, v nichž je potom méně stringů než by bylo pokud by byly všechny v jedné Dictionary.
			/// Hodnota Group se fyzicky neukládá odděleně, ale ukládá se do horních bitů Pointeru (bity 23,24,25,26,27,28,29,30). Bit 31 = záporné číslo.
            /// </summary>
            internal Int32 Group
			{
				get
				{
					if (IsSimple) return 0;
					return CreateGroup(this.Pointer);
				}
				private set
				{
					if (value <= 0) return;                   // Záporné hodnoty nelze vepsat
					int group = value & _MASK_LGROUP;         // Grupy 0-255
					if (group == 0) group = _MASK_LGROUP;     // Grupy 1-255
					this.Pointer = (group << _BIT_GROUP) | (this.Pointer & _MASK_INDEX);    // Grupa do bitů 23-30, z pointeru ponechám dolní bity 0-22 = Index
				}
			}
            /// <summary>
			/// Index je určen pro uložení fyzického indxu v jednoduchém poli StringAxis.Values[index], má hodnotu 0 až cca 8 000 000 (0x7FFFFF = 8 388 607)
            /// Po sloučení s Group vznikne Pointer.
            /// Hodnoty Group záporné nepoužívají Index, fyzicky se neukládá string.
            /// </summary>
            internal Int32 Index
			{
				get
				{
					if (IsSimple) return -1;
					return (this.Pointer & _MASK_INDEX);
				}
				set
				{
					if (IsSimple) return;
					this.Pointer = (this.Pointer & _MASK_GROUP) | (value & _MASK_INDEX);   // Nechám horní bity = Group a přidám dolní bity = index
				}
			}
			/// <summary>
			/// Bit shiftu masky = 23
			/// </summary>
			private const int _BIT_GROUP = 23;
			/// <summary>
			/// Maska na Group = 0xFF (jednotková)
			/// </summary>
			private const int _MASK_LGROUP = 0xFF;
			/// <summary>
			/// Maska na Group = 0x7F800000 (když je Group v pozici bitů 23 - 30, již v Pointeru)
			/// </summary>
			private const int _MASK_GROUP = 0x7F800000;
			/// <summary>
			/// Maska na Index = 0x7FFFFF
			/// </summary>
			private const int _MASK_INDEX = 0x007FFFFF;
			#endregion
			#region Statické podpůrné metody
			/// <summary>
			/// Vytvoří a vrátí string pro záporný pointer (tj. null a empty stringy).
			/// </summary>
			/// <param name="pointer"></param>
			/// <returns></returns>
			internal static string CreateString(int pointer)
			{
				if (pointer == 0) return null;
				if (pointer > 0) throw new InvalidOperationException("Nelze použít metodu StringInfo.CreateString() pro kladný pointer.");

				int ptr = -pointer;
				if (ptr >= 65537) return "".PadRight(ptr - 65537);     // hodnota 65537 odpovídá stringu s délkou 0, větší hodnoty => větší délka stringu 
				// hodnota 1 odpovídá char(0), hodnota 65536 odpovídá char(0xFFFF):
				return ((char)(ptr - 1)).ToString();
			}
			/// <summary>
			/// Vrátí číslo skupiny z daného pointeru
			/// </summary>
			/// <param name="pointer"></param>
			/// <returns></returns>
			internal static int CreateGroup(int pointer)
			{
				if (pointer < 0) return 0;
				return ((pointer >> _BIT_GROUP) & _MASK_LGROUP);
			}
            #endregion
            #region Overrides ToString(), GetHashCode(), Equals()
            public override string ToString()
            {
                return this.Value;
            }
            public override int GetHashCode()
            {
                return this.Hash;
            }
            public override bool Equals(object obj)
            {
                StringInfo other = obj as StringInfo;
                if (other == null) return false;
                if (this.Group != other.Group || this.Group == -1) return false;   // Jiný druh stringů = neshoda, anebo shodný druh typu null = taky neshoda (null != null)
                if (this.Hash != other.Hash ) return false;                        // Jiný Hash = neshoda
                if (this.Group == -2) return true;                                 // Pokud je hodnota na obou stranách Empty (-2), a shoduje se Hash ( = délka), pak jsou oba stringy shodné (vracím true)

                // Oba stringy jsou shodného druhu, null a empty jsou vyřešené, zbývá tedy reálný string:
                return String.Equals(this._Value, other._Value);
            }
            #endregion
        }
		#endregion
    }
	#endregion
	#region class StringInfo : úložiště jednoho konkrétního stringu + statické metody pro konverzi jednoduchých stringů
	/// <summary>
	/// Pointer na string.
	/// Zahrnuje v sobě jak číslo (ponter) stringu, tak referenci na StringHeap, který je správcem slovníku stringů.
	/// Bez jakékoli další pomoci dokáže kdykoliv vrátit string, který reprezentuje.
	/// Platí se za to velikostí: instance této třídy má velikost 16 Byte. Je to méně než string, ale více než je minimálně nutné.
	/// Pro největší možnou úsporu paměti se na místo stringu ukládá holý pointer (Int32 = 4 Byte), 
	/// a reference na StringHeap se ukládá 1x za vhodnou nadřízenou instanci. 
	/// Vlastní stringy se reprezentují pomocí property:
	/// ( get { return StringHeap.GetString(this._Pointer); } set { this._Pointer = StringHeap.GetInt32(value); } }
	/// </summary>
    internal class StringPtr
    {
        internal StringPtr(StringHeap stock, int pointer)
        {
            this.Stock = stock;
            this.Pointer = pointer;
        }
        private StringHeap Stock;
        private int Pointer;
        public override string ToString()
        {
            return this.Value;
        }
        public string Value { get { return this.Stock.GetString(this.Pointer); } }
    }
	#endregion
}
